#include "Oauth.h"
//#include <wx/datetime.h>
#include "GlobalConfig.h"
#include <cmath>
#include <algorithm>
#include <wx/base64.h>
#include <wx/thread.h>
#include <ctime>
#include "Utils.h"


extern "C"
{
    void hmac_sha(const char* k, int lk, const char* d, int ld,  char* out, int t);
}

bool isLessThan(const std::pair<wxString, wxString>& obj1, const std::pair<wxString, wxString>& obj2)
{
    if(obj1.first==obj2.first)
    {
        return obj1.second<obj2.second;
    }
    else
    {
        return obj1.first<obj2.first;
    }
}

///-------------------------------------------------------------------

OauthData::OauthData()
:consumer_key(APP_KEY)
,consumer_secret(APP_SECRET)
,signature_method(SIGN_METHOD)
,version(OAUTH_VERSION)
,timestamp("")
,nonce("")
,signature("")
,verifier("")
,callback("")
,oauth_token("")
,oauth_secret("")
,mpBuildDataCS(new wxCriticalSection())
{

}

OauthData::~OauthData()
{
    if(mpBuildDataCS!=NULL)
    {
        delete mpBuildDataCS;
        mpBuildDataCS = NULL;
    }
}

/*
bool OauthData::validateDataFormat(OAuthStateType state)
{
    OAuthParamDataType::iterator itr;
    OAuthParamDataType::iterator itr_begin = packagedData.begin();
    OAuthParamDataType::iterator itr_end = packagedData.end();

    switch(state)
    {
        case OAUTH_REQUEST_TOKEN_PREPARE:
        {
            for(itr = itr_begin; itr!=itr_end;++itr)
            {
                if(itr->first=="oauth_verifier" ||
                   itr->first=="oauth_token")
                {
                    #ifdef OAUTH_LOG
                    LOGMAN.logError(wxString("When verifying oauth data format, occured unexpected item: ")<< itr->first);
                    #endif
                    return false;
                }
            }
            return true;
        }

        case OAUTH_ACCESS_TOKEN_PREPARE:
        {
            enum OccurenceFlagType
            {
                VerifierFoundFlag = 0,
                TokenFoundFlag = 1,

                FlagMax = 2,
            };

            uint8_t result = 0x0;

            for(itr = itr_begin; itr!=itr_end;++itr)
            {
                //Find empty item
                if( (itr->first=="oauth_verifier" && itr->second.IsEmpty()) ||
                   (itr->first=="oauth_token" && itr->second.IsEmpty()))
                {
                    #ifdef OAUTH_LOG
                    LOGMAN.logError(wxString("When verifying oauth data format, empty item found: ")<< itr->first);
                    #endif
                    return false;
                }

                //Check lost items
                if(itr->first=="oauth_verifier") result = result | (1<<VerifierFoundFlag);
                if(itr->first=="oauth_token") result = result | (1<<TokenFoundFlag);
            }
            return result==(1<<FlagMax)-1;
        }

        case OAUTH_ACCESS_TOKEN_DONE:
        {
            for(itr = itr_begin; itr!=itr_end;++itr)
            {
                //Find empty item
                if(itr->first=="oauth_token" && ! itr->second.IsEmpty())
                {
                    #ifdef OAUTH_LOG
                    LOGMAN.logError("When verifying oauth data format, found oauth_token is empty: ");
                    #endif
                    return false;
                }
            }
            return true;
        }

        default:
        {
            assert(false);
        }
    }

    return false;
}
*/

void OauthData::buildParamData()
{
    wxCriticalSectionLocker buildDataCSLocker(*mpBuildDataCS);
    packagedData.clear();

    packagedData.push_back(std::make_pair("oauth_consumer_key",consumer_key));
    packagedData.push_back(std::make_pair("oauth_signature_method",signature_method));
    packagedData.push_back(std::make_pair("oauth_timestamp",timestamp));
    packagedData.push_back(std::make_pair("oauth_nonce",nonce));
    packagedData.push_back(std::make_pair("oauth_version",version));

    if(! signature.IsEmpty())
        packagedData.push_back(std::make_pair("oauth_signature",signature));
    if(! oauth_token.IsEmpty())
        packagedData.push_back(std::make_pair("oauth_token",oauth_token));
    if(! verifier.IsEmpty())
            packagedData.push_back(std::make_pair("oauth_verifier",verifier));
    if(! callback.IsEmpty())
            packagedData.push_back(std::make_pair("oauth_callback",callback));

    ///extra query params should take part in the base string and signature but no occurance in auth header
    std::map<wxString, wxString>::iterator itr = extraParams.begin();
    for(; itr!=extraParams.end(); ++itr)
    {
        packagedData.push_back(*itr);
    }
}


///-------------------------------------------------------------------------------

Oauth::Oauth()
:mOauthData(NULL)
,mBaseString("")
,mOauthHeader("")
,accessPair(std::make_pair("", ""))
,mpBaseStringCS(new wxCriticalSection())
,mpSignatureCS(new wxCriticalSection())
,mpHeaderCS(new wxCriticalSection())
{
    //ctor
    initData();
}

Oauth::~Oauth()
{
    //dtor
    destroyData();

    if(NULL != mpBaseStringCS) delete mpBaseStringCS;
    if(NULL != mpSignatureCS) delete mpSignatureCS;
    if(NULL != mpHeaderCS) delete mpHeaderCS;

    mpBaseStringCS = NULL;
    mpSignatureCS = NULL;
    mpHeaderCS = NULL;
}

Oauth& Oauth::GetInstance()
{
    static Oauth oauthObj;
    return oauthObj;
}

/******************Oauth : Helper functions************************/

void Oauth::initData()
{
    if(mOauthData!=NULL)
    {
        mOauthData->reset();
    }
    else
    {
        mOauthData = new OauthData();
    }

    if(! (accessPair.first.IsEmpty() || accessPair.second.IsEmpty()) )
    {
        mOauthData->oauth_token = accessPair.first;
        mOauthData->oauth_secret = accessPair.second;
    }
}

void Oauth::destroyData()
{
    if(NULL == mOauthData) return;
    delete mOauthData;
    mOauthData = NULL;
}

void Oauth::setExtraQueryParams(const wxString& key, const wxString& val)
{
    if(NULL != mOauthData && !key.IsEmpty())
    {
        assert(mOauthData->extraParams.count(key)==0);
        mOauthData->extraParams[key] = val;
    }
}

void Oauth::setExtraQueryParams(const std::map<wxString, wxString>& paramTable)
{
    if(NULL != mOauthData && !paramTable.empty())
    {
        mOauthData->extraParams.clear();
        mOauthData->extraParams.insert(paramTable.begin(), paramTable.end());
    }
}

const wxString& Oauth::getBaseString(const wxString& baseURL, const wxString& httpMethod, bool isUnique)
{
    wxCriticalSectionLocker baseStringCSLocker(*mpBaseStringCS);

    if(NULL != mOauthData)
    {
        mBaseString.Clear();

        if(isUnique)
        {
            mOauthData->timestamp.Clear();
            mOauthData->timestamp<<getTimestamp();
            mOauthData->nonce.Clear();
            mOauthData->nonce<<getNonce();
        }

        mOauthData->buildParamData();
        std::sort(mOauthData->packagedData.begin(),
                    mOauthData->packagedData.end(),
                    isLessThan);

        std::vector< std::pair<wxString, wxString> >::iterator itr;
        std::vector< std::pair<wxString, wxString> >::iterator itr_begin=mOauthData->packagedData.begin();
        const wxString seperator("&");
        const wxString equal("=");
        for(itr=itr_begin; itr!=mOauthData->packagedData.end(); ++itr)
        {
            //base string should not contain signature even if packaged data provide it
            if(itr->first=="oauth_signature") continue;
            //if(itr->first=="oauth_callback" && baseURL==ACCESS_TOKEN_API) continue; //callback is neither required nor prohibit in access token req
            if(itr->second.IsEmpty())
            {
                assert(false);  //in case that unnecessary params involved in packagedData
                #ifdef OAUTH_LOG
                LOGMAN.logError("Empty item found in base string when OAuth");
                #endif
            }

            if(itr!=itr_begin)
            {
                mBaseString<<seperator;
            }
            mBaseString<<itr->first;
            mBaseString<<equal;
            mBaseString<<UT_URLENCODE_UTF8(itr->second);
        }

        mBaseString = UT_URLENCODE_UTF8(mBaseString);
        mBaseString.Prepend(httpMethod+wxString("&")
                    <<UT_URLENCODE_UTF8(baseURL)
                    <<wxString("&"));
    }
    else
    {
        #ifdef OAUTH_LOG
        LOGMAN.logError("mOauthData==NULL when getBaseString"); //wtf
        #endif
        assert(false);
    }

    return mBaseString;
}

void Oauth::signRequest(const wxString& baseStr)
{
    wxCriticalSectionLocker signatreCSLocker(*mpSignatureCS);

    if(NULL != mOauthData)
    {
        char dest[OAUTH_SHA1_BUFFER_SIZE];
        memset( dest, 0, OAUTH_SHA1_BUFFER_SIZE );
        wxString key(mOauthData->consumer_secret+"&"+mOauthData->oauth_secret);
        hmac_sha( key.utf8_str(),
                        strlen(key.utf8_str()),
                        baseStr.utf8_str(),
                        strlen(baseStr.utf8_str()),
                        dest,
                        OAUTH_SHA1_BUFFER_SIZE);

        //dest[OAUTH_SHA1_BUFFER_SIZE-1]=0;     //avoid buffer overflow
        mOauthData->signature = UT_URLENCODE_UTF8(wxBase64Encode(dest, strlen(dest)));
        //refresh packagedData to add oauth_signature
        ///DO NOT manually push_back , this may duplicate an existed signature item in packagedData
        mOauthData->buildParamData();
    }
    else
    {
        #ifdef OAUTH_LOG
        LOGMAN.logError("mOauthData==NULL when signRequest"); //wtf
        #endif
    }
}


const wxString& Oauth::getOauthHeader()
{
    wxCriticalSectionLocker headerCSLocker(*mpHeaderCS);
    mOauthHeader.Clear();
    mOauthHeader<<wxString("OAuth realm=\"\",");

    if(NULL != mOauthData)
    {
        //sorted params is not a requirement
        //std::sort(mOauthData->packagedData.begin(), mOauthData->packagedData.end(),isLessThan);

        bool signatureFound = false;
        std::vector< std::pair<wxString, wxString> >::iterator itr;
        std::vector< std::pair<wxString, wxString> >::iterator itr_begin=mOauthData->packagedData.begin();
        const wxString seperator(",");
        const wxString equal("=");
        size_t initHeaderLength = mOauthHeader.Length();
        for(itr=itr_begin; itr!=mOauthData->packagedData.end(); ++itr)
        {
            //extra query params in the auth header will raise a invalid signaure error
            if(mOauthData->extraParams.count(itr->first)>0)
                continue;

            if(mOauthHeader.Length()>initHeaderLength)
            {
                mOauthHeader << seperator;
            }
            mOauthHeader << itr->first;
            mOauthHeader << equal;
            mOauthHeader<< wxString("\"") << itr->second<< wxString("\"");

            //verify the data contains signature
            if(itr->first=="oauth_signature" && itr->second != "") signatureFound = true;
        }//end of for(mOauthData)

        if(! signatureFound)
        {
            #ifdef OAUTH_LOG
            LOGMAN.logError("OAuth signature not found when getOauthHeader"); //wtf
            #endif
            assert(false);
        }
    }
    else
    {
        #ifdef OAUTH_LOG
        LOGMAN.logError("mOauthData==NULL when getOauthHeader"); //wtf
        #endif
    }

    return mOauthHeader;
}


